package com.example.springcloudgateway.encrypt;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import static org.apache.commons.codec.binary.Base64.decodeBase64;
import static org.apache.commons.codec.binary.Base64.encodeBase64String;

/**
 * detail: https://github.com/keel/aes-cross
 *
 * @author yijie
 */
public class Aes {
    final private static String CHARSET = "utf-8";
    final private static String SPEC = "AES";
    final private static String INSTANCE = "AES/CBC/PKCS5Padding";
    final private static int LEN = 16;
    /**
     * size must be 16
     */
    final private static byte[] DEFAULT_KEY = {1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8};
    /**
     * size must be 16
     */
    final private static byte[] DEFAULT_IVK = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    /**
     * encrypt with default key and ivk
     *
     * @param srcStr source string
     * @return encrypt string
     * @throws Exception exception
     */
    public static String encrypt(String srcStr) throws Exception {
        return DefaultInstance.encText(srcStr);
    }

    /**
     * decrypt with default key and ivk
     *
     * @param srcStr source string
     * @return decrypt string
     * @throws Exception exception
     */
    public static String decrypt(String srcStr) throws Exception {
        return DefaultInstance.decText(srcStr);
    }

    /**
     * encrypt bytes with default key and ivk
     *
     * @param srcBytes source bytes
     * @return encrypt bytes
     * @throws Exception exception
     */
    public static byte[] encryptBytes(byte[] srcBytes) throws Exception {
        return DefaultInstance.encBytes(srcBytes);
    }

    /**
     * decrypt bytes with default key and ivk
     *
     * @param srcBytes source bytes
     * @return decrypt bytes
     * @throws Exception exception
     */
    public static byte[] decryptBytes(byte[] srcBytes) throws Exception {
        return DefaultInstance.decBytes(srcBytes);
    }


    /**
     * encrypt with default ivk
     *
     * @param srcStr source string
     * @param key    key
     * @return encrypt string
     * @throws Exception exception
     */
    public static String encrypt(String srcStr, String key) throws Exception {
        return encrypt(srcStr, buildKeyBytes(key), DEFAULT_IVK);
    }

    /**
     * decrypt with default ivk
     *
     * @param srcStr source string
     * @param key    key
     * @return decrypt string
     * @throws Exception exception
     */
    public static String decrypt(String srcStr, String key) throws Exception {
        return decrypt(srcStr, buildKeyBytes(key), DEFAULT_IVK);
    }

    /**
     * encrypt bytes with default ivk
     *
     * @param srcBytes source bytes
     * @param key      key bytes
     * @return encrypt bytes
     * @throws Exception exception
     */
    public static byte[] encryptBytes(byte[] srcBytes, byte[] key) throws Exception {
        return encryptBytes(srcBytes, buildKeyBytes(key), DEFAULT_IVK);
    }

    /**
     * decrypt bytes with default ivk
     *
     * @param srcBytes source bytes
     * @param key      key bytes
     * @return decrypt bytes
     * @throws Exception exception
     */
    public static byte[] decryptBytes(byte[] srcBytes, byte[] key) throws Exception {
        return decryptBytes(srcBytes, buildKeyBytes(key), DEFAULT_IVK);
    }


    public static String encrypt(String sSrc, byte[] key, byte[] newIv) throws Exception {
        byte[] srcBytes = sSrc.getBytes(CHARSET);
        byte[] encrypted = encryptBytes(srcBytes, key, newIv);
        return encodeBase64String(encrypted);
    }

    public static String decrypt(String sSrc, byte[] key, byte[] newIv) throws Exception {
        byte[] srcBytes = decodeBase64(sSrc);
        byte[] decrypted = decryptBytes(srcBytes, key, newIv);
        return new String(decrypted, CHARSET);
    }

    public static byte[] encryptBytes(byte[] srcBytes, byte[] key, byte[] newIv) throws Exception {
        Cipher cipher = Cipher.getInstance(INSTANCE);
        SecretKeySpec skeySpec = new SecretKeySpec(key, SPEC);
        IvParameterSpec iv = new IvParameterSpec(newIv);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        return cipher.doFinal(srcBytes);
    }

    public static byte[] decryptBytes(byte[] srcBytes, byte[] key, byte[] newIv) throws Exception {
        Cipher cipher = Cipher.getInstance(INSTANCE);
        SecretKeySpec skeySpec = new SecretKeySpec(key, SPEC);
        IvParameterSpec iv = new IvParameterSpec(newIv);
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
        return cipher.doFinal(srcBytes);
    }


    /**
     * build key bytes which length is 16
     *
     * @param key key
     * @return key bytes
     */
    private static byte[] buildKeyBytes(String key) {
        return buildKeyBytes(key.getBytes());
    }

    /**
     * build key bytes which length is 16
     *
     * @param bytes key bytes
     * @return key bytes
     */
    private static byte[] buildKeyBytes(byte[] bytes) {
        return buildIvkBytes(bytes);
    }

    /**
     * build key bytes which length is 16
     *
     * @param bytes ivk bytes
     * @return ivk bytes
     */
    private static byte[] buildIvkBytes(byte[] bytes) {
        byte[] bs;
        if (bytes.length > LEN) {
            bs = new byte[LEN];
            System.arraycopy(bytes, 0, bs, 0, LEN);
        } else if (bytes.length < LEN) {
            bs = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
            System.arraycopy(bytes, 0, bs, 0, bytes.length);
        } else {
            bs = bytes;
        }
        return bs;
    }

    private static class DefaultInstance {
        private static Cipher encCipher;
        private static Cipher decCipher;

        static {
            try {
                encCipher = Cipher.getInstance(INSTANCE);
                decCipher = Cipher.getInstance(INSTANCE);
                SecretKeySpec keySpec = new SecretKeySpec(DEFAULT_KEY, SPEC);
                IvParameterSpec ivc = new IvParameterSpec(DEFAULT_IVK);
                encCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivc);
                decCipher.init(Cipher.DECRYPT_MODE, keySpec, ivc);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public static String encText(String srcStr) throws Exception {
            byte[] srcBytes = srcStr.getBytes(CHARSET);
            byte[] encrypted = encBytes(srcBytes);
            return encodeBase64String(encrypted);
        }

        public static String decText(String srcStr) throws Exception {
            byte[] srcBytes = decodeBase64(srcStr);
            byte[] decrypted = decBytes(srcBytes);
            return new String(decrypted, CHARSET);
        }

        public static byte[] encBytes(byte[] srcBytes) throws Exception {
            return encCipher.doFinal(srcBytes);
        }

        public static byte[] decBytes(byte[] srcBytes) throws Exception {
            return decCipher.doFinal(srcBytes);
        }
    }

}
